﻿using System;
using System.Net;
using System.Net.Sockets;

using COUP.Core;
using COUP.Core.Enums;
using COUP.Core.Native;
using COUP.Core.Networking;

namespace COUP.Game.Networking
{
    public class ConnectionHandler
    {
        public event PacketEventHandler Handle;

        private bool _AllowConnections = false;

        readonly Socket Connection;
        readonly IPEndPoint EndPoint;
        readonly int BufferSize;
        readonly int Port;

        public ConnectionHandler(int _Port = 9958)
        {
            //Declare Readonly Values
            Port = _Port;
            BufferSize = 1024;
            EndPoint = new IPEndPoint(IPAddress.Any, Port);
            Connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //

            Configure();
        }

        public bool AllowConnections
        {
            get { return _AllowConnections; }
            set
            {
                _AllowConnections = value;
                if (value)
                {
                    Listen();
                }
            }
        }

        internal void Configure()
        {
            //Configures the Socket
            Connection.Bind(EndPoint);
            Connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
            Connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);              
        }

        internal void Listen()
        {
            //Begins listening on the socket for incomming connections
            Connection.Listen(100);
            Program.Output("Listening on Port " + Port);
            Connection.BeginAccept(new AsyncCallback(onConnection), null);
        }       

        internal void onConnection(IAsyncResult res)
        {
            //Creates the ClientState object which will store all of our connection info
            ClientState State = new ClientState(Connection.EndAccept(res), BufferSize, ServerType.Game, Program.Version, Program.Database);
            //Resets the master socket to allow further connections
            Connection.BeginAccept(new AsyncCallback(onConnection), null);
            //Configures the ClientState socket
            State.Connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
            State.Connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
            State.Buffer = null;
            State.Header = new byte[4];
            //Begins listening for data on the ClientState socket
            State.Connection.BeginReceive(State.Header, 0, 4, SocketFlags.None,
                new AsyncCallback(onReceiveHeader), State);
        }

        internal unsafe void onReceiveHeader(IAsyncResult res)
        {
            SocketError socError;
            //Gets the state object
            ClientState State = (res.AsyncState as ClientState);
            //Checks if the state object exists
            if (State == null)
                return;
            //Checks if the state socket is connected
            if (!State.Connection.Connected)
                return;
            //Gets the state socket to be worked on locally
            Socket ClientConn = State.Connection;
            //Signals the socket to stop receiving and tell us how much data we need to handle
            int Recv = ClientConn.EndReceive(res, out socError);
            //Checks to see if we have data to handle (we should since onReceive was called
            //And checks there werent any errors on the socket, if there were we should abandon
            if (Recv == 0 || socError != SocketError.Success)
                return;
            //Decrypts the received Header
            State.Cryptographer.Decrypt(ref State.Header);
            //Creates the pointer to access the data in the packet
            fixed (byte* lpHeader = State.Header)
            {
                //Retrieves the length from the packet using a pointer
                int Length = (ushort)*(ushort*)lpHeader;

                if (Length > 400)
                    Program.Output("Large Packet Detected!");                        
                //Creates the array to hold the body of the packet
                State.Body = new byte[Length - 4];
                //Tells the socket to retreieve the next packet based on the length we just received
                State.Connection.BeginReceive(State.Body, 0, Length - 4, SocketFlags.None,
                    new AsyncCallback(onReceiveBody), State);
            }
        }

        internal void onReceiveBody(IAsyncResult res)
        {
            SocketError socError;
            //Gets the state object
            ClientState State = (res.AsyncState as ClientState);
            //Checks if the state object exists
            if (State == null)
                return;
            //Checks if the state socket is connected
            if (!State.Connection.Connected)
                return;
            //Gets the state socket to be worked on locally
            Socket ClientConn = State.Connection;
            //Signals the socket to stop receiving and tell us how much data we need to handle
            int Recv = ClientConn.EndReceive(res, out socError);
            //Checks to see if we have data to handle (we should since onReceive was called
            //And checks there werent any errors on the socket, if there were we should abandon
            if (Recv == 0 || socError != SocketError.Success)
                return;
            //Decrypts the body of the packet
            State.Cryptographer.Decrypt(ref State.Body);
            //Creates a new array based on the header
            byte[] CompletePacket = State.Header;
            //Constructs the complete packet by adding the body to thhe header
            Append(ref CompletePacket, State.Body);
            //Resets the header and body containers in the state object
            State.Header = new byte[4];
            State.Body = null;
            //Handles the completed packet
            Handle.Invoke(State, new Packet(CompletePacket));

            //Resets the client socket to begin receiving more data
            State.Connection.BeginReceive(State.Header, 0, 4, SocketFlags.None,
                new AsyncCallback(onReceiveHeader), State);
        }

        internal unsafe void Append(ref byte[] dest, byte[] src)
        {
            byte[] temp = new byte[dest.Length + src.Length];
            fixed (byte* lpDest = dest, lpSrc = src, lpTemp = temp)
            {
                msvcrt.memcpy(lpTemp, lpDest, dest.Length);
                msvcrt.memcpy(lpTemp + dest.Length, lpSrc, src.Length);
            }
            dest = temp;
        }
    }
}
